#!/bin/bash

VERSION=1.2

# Avoid using these variables globally, pass them to the functions if needed
INDIR=""  # Valid input directory set by set_input_directory
INARC=""  # Valid input archive set by set_input_archive
OUTDIR="$(pwd)/cytar"  # Valid default output directory path if not set by set_output_directory method
OPTT=-1  # Counter that ensures, that the user did not pass compress and decompress flags simultaneously

SPLITMB=500  # Split size in MB
NAME=cytar  # Archive filename without suffix
MODE=tar  # either tar or 7z container reference

QUICK=0  # Quick mode boolean that skips checksum calculation and compresses without content.tar
CHECKSUMBINARY=sha512sum  # Checksum binary found in $PATH
CHECKSUMNAME=sha512  # Checksum algo name for checksum file suffix

CIPHERALGO=""  # GPG cipher algorithm - encryption is disabled by default
SZENC=""  # Populate with 7z promtless encryption flags if required (wip)
GPGENC=""  # Populate with gpg promtless encryption flags if required (wip)

compress () {
	# $1 = Input directory
	# $2 = Output directory
	echo "[cytar] compressing \"$1\" to "$SPLITMB"MB splits to \"$2\""
	if [[ $MODE == tar ]]
	then
		if [[ $QUICK -eq 0 ]]
		then
			mkdir -p "$2/.tmpcytar"
			cd "$2/.tmpcytar"

			echo "[cytar] compressing contents..."
			if ! tar -C "$1" -cf content.tar .
			then
				echo "[cytar] [ERROR] an error occured while compressing content.tar!"
				exit 1
			fi

			echo "[cytar] calculating $CHECKSUMNAME checksum of content.tar..."
			if ! $CHECKSUMBINARY content.tar > content.$CHECKSUMNAME
			then
				echo "[cytar] [ERROR] an error occured while calculating $CHECKSUMNAME checksums with $CHECKSUMBINARY!"
				exit 1
			fi

			if [[ $CIPHERALGO != "" ]]
			then
				echo "[cytar] encrypting content.tar with gpg..."
				if ! gpg $GPGENC--cipher-algo $CIPHERALGO -c --output content.tar.gpg content.tar
				then
					echo "[cytar] [ERROR] an error occured while encrypting content.tar!"
					exit 1
				fi

				echo "[cytar] calculating $CHECKSUMNAME checksum of encrypted content.tar.gpg..."
				if ! $CHECKSUMBINARY content.tar.gpg >> content.$CHECKSUMNAME
				then
					echo "[cytar] [ERROR] an error occured while calculating $CHECKSUMNAME checksums with $CHECKSUMBINARY!"
					exit 1
				fi

				echo "[cytar] deleting content.tar..."
				rm content.tar
			fi

			echo "[cytar] writing split tar files..."
			if ! tar -cf - . | split --bytes="$SPLITMB"m --suffix-length=3 --numeric-suffix - "$2"/"$NAME".tar.
			then
				echo "[cytar] [ERROR] an error occured while writing split tar files!"
				exit 1
			fi
			cd ..
			rm -r .tmpcytar
		elif [[ $QUICK -eq 1 ]]
		then
			echo "[cytar] operating in quick mode"
			echo "[cytar] writing split tar files directly..."
			if ! tar -C "$1" -cf - . | split --bytes="$SPLITMB"m --suffix-length=3 --numeric-suffix - "$2"/"$NAME".tar.
			then
				echo "[cytar] [ERROR] an error occured while writing split tar files!"
				exit 1
			fi
		fi
	elif [[ $MODE == 7z ]]
	then
		echo "[cytar] operating in 7zip mode"
		echo "[cytar] compressing content with 7zip..."
		cd "$1"
		if ! 7z -v"$SPLITMB"m a "$2/$NAME".7z -r . -m0=Copy -mx=0 -mhe=on -mmt=on$SZENC
		then
			echo "[cytar] [ERROR] an error occured while compressing content with 7zip!"
			exit 1
		fi
		[[ $QUICK -eq 0 ]] && test_7z_archive "$2/$NAME.7z.001"
	fi

	cd "$2"
	if [[ $QUICK -eq 0 ]]
	then
		echo "[cytar] calculating $CHECKSUMNAME checksums of split archives..."
		if ! $CHECKSUMBINARY "$NAME".$MODE.* > "$NAME".$CHECKSUMNAME
		then
			echo "[cytar] [ERROR] an error occured while calculating $CHECKSUMNAME checksums with $CHECKSUMBINARY!"
			exit 1
		fi
	fi	

	echo "[cytar] compressing finnished!"
	exit 0
}

decompress () {
	# $1 = Input directory
	# $2 = Output directory
	NAME=$(basename -- "$1")
	NAME="${NAME::-4}"
	if [[ "${NAME: -3}" == ".7z" ]]
	then
		echo "[cytar] switched to 7zip mode"
		MODE=7z
	elif [[ ! -f "$1" || "${NAME: -4}" != ".tar" ]]
	then
		echo "[cytar] [ERROR] invalid archive: $1"
		exit 1
	fi

	cd "$(dirname "$1")"
	echo "[cytar] decompressing split archive \"$NAME\" (in \"$1\") to \"$2\""
	if [[ $MODE == 7z ]]
	then
		[[ $QUICK -eq 0 ]] && test_7z_archive "$1"
		if 7z x "$1" -o"$2"
		then
			echo "[cytar] decompressing finnished!"
			exit 0
		else
			echo "[cytar] [ERROR] decompressing failed!"
			exit 1
		fi
	fi

	echo "[cytar] decompressing split archives..."
	if ! cat "$NAME".* | tar -C "$2" -xf -
	then
		echo "[cytar] [ERROR] decompression of split archives failed!"
		exit 1
	else
		echo "[cytar] split archives successfully decompressed!"
		if [[ $QUICK -eq 1 ]] || [[ ! -f "$2"/content.tar && ! -f "$2"/content.tar.gpg ]]
		then
			echo "[cytar] operating in quick mode, exitting early!"
			exit 0
		fi
	fi

	cd "$2"
	set_checksum_algo "$(ls . | grep content | grep -v tar)" content
	if [[ $CIPHERALGO != "" || -f "$2"/content.tar.gpg ]]
	then
		echo "[cytar] decrypting content.tar.gpg with gpg..."
		if ! gpg $GPGENC-d --output content.tar content.tar.gpg
		then
			echo "[cytar] [ERROR] an error occured while decrypting content.tar!"
			exit 1
		fi

		echo "[cytar] calculating $CHECKSUMNAME checksum of decrypted content..."
		$CHECKSUMBINARY content.tar > content.new.$CHECKSUMNAME
		$CHECKSUMBINARY content.tar.gpg >> content.new.$CHECKSUMNAME
		rm content.tar.gpg
	else
		echo "[cytar] calculating $CHECKSUMNAME checksum of decrypted content..."
		$CHECKSUMBINARY content.tar > content.new.$CHECKSUMNAME
	fi

	echo "[cytar] performing checksum test..."
	if cmp -s "content.$CHECKSUMNAME" "content.new.$CHECKSUMNAME"
	then
		echo "[cytar] checksum test passed!"
		rm content.new.$CHECKSUMNAME
		rm content.$CHECKSUMNAME
	else
		echo "[cytar] [ERROR] checksum test failed!"
		exit 1
	fi

	echo "[cytar] decompressing content..."
	if tar -xf content.tar
	then
		rm content.tar
		echo "[cytar] decompressing finnished!"
	else
		echo "[cytar] [ERROR] decompressing failed!"
		exit 1
	fi
	exit 0
}

test_7z_archive () {
	echo "[cytar] testing 7zip archive..."
	if 7z -v"$SPLITMB"m t "$1"
	then
		echo "[cytar] 7zip archive passed testing!"
	else
		echo "[cytar] [ERROR] 7zip archive did not pass testing!"
		exit 1
	fi
}

set_checksum_algo () {
	if [[ $2 == content ]]
	then
		# internal function call
		CHECKSUMNAME="${1##*.}"
		CHECKSUMBINARY="$CHECKSUMNAME"sum
	else
		# external function call
		CHECKSUMBINARY=$1sum
		CHECKSUMNAME=$1
	fi
	
	if [ -x /usr/bin/$CHECKSUMBINARY ]
	then
		echo "[cytar] using $CHECKSUMNAME (in /usr/bin/$CHECKSUMBINARY) as checksum algorithm"
	else
		echo "[cytar] [ERROR] invalid checksum algorithm: $1"
		exit 1
	fi
}

set_output_directory () {
	if [ ! -d "$1" ]
	then
		mkdir -p "$1"
	fi
	if [ -z "$(ls -A "$1")" ]
	then
		OUTDIR="$1"
	else
		echo "[cytar] [ERROR] invalid output directory (must be empty!): $1"
		exit 1
	fi
}

set_input_directory () {
	if [ -d "$1" ]
	then
		INDIR="$1"
	else
		echo "[cytar] [ERROR] invalid input directory: $1"
		exit 1
	fi
}

set_input_archive () {
	if [ -e "$1" ]
	then
		INARC="$1"
	else
		echo "[cytar] [ERROR] invalid input archive: $1"
		exit 1
	fi
}

set_encryption () {
	if [[ $2 == e ]]
	then
		SZENC=" -p"
		if [[ $1 == aes256 || $1 == twofish ]]
		then
			echo "[cytar] using $1 as gpg encryption algorithm"
			CIPHERALGO=$1
		elif [[ $1 != "" ]]
		then
			echo "[cytar] [ERROR] invalid gpg encryption algorithm: $1"
			exit 1
		fi
	elif [[ $2 == p ]]
	then
		echo "[cytar] setting promptless encryption"
		SZENC=" -p$1"
		GPGENC="--batch --passphrase "$1" --pinentry-mode loopback "
	fi
}

print_help () {
	printf " \
-7                            Use 7zip (no .tmpcytar required)\n \
-q                            Quick mode: no encryption, no checksums (no .tmpcytar required)\n \
-a [md5,sha1,sha256,sha512]   Checksum algorithm (default: sha512)\n \
-e [aes256,twofish]           Enable encryption (7z always uses 7zAES) (tar requires 2x .tmpcytar space)\n \
-p \"mypassword\"               Promptless encryption password (insecure, no spaces, avoid special characters, unstable!)\n \
-s 500                        Split size in MB\n \
-n \"cyarchive\"                Archive name\n \
-o /path/to/folder            Output directory MUST BE EMPTY (default: ./cytar) (also .tmpcytar directory)\n\n \
-c /path/to/folder            Compress folder\n \
OR\n \
-d /path/to/archive.tar.000   Decompress archive\n"
	exit 0
}

# Print version
echo "cytar version $VERSION"

# Get user input flags
while getopts s:n:o:c:d:a:p:e:q7h OPTION
do
	case "${OPTION}"
	in
		h) print_help ;;
		7) MODE=7z ;;
		q) QUICK=1 ;;
		a) set_checksum_algo "${OPTARG}" ;;
		e) set_encryption "${OPTARG}" e ;;
		p) set_encryption "${OPTARG}" p ;;
		s) SPLITMB="${OPTARG}" ;;
		n) NAME="${OPTARG}" ;;
		o) set_output_directory "$(readlink -f "${OPTARG}")" ;;
		c) set_input_directory "$(readlink -f "${OPTARG}")" && ((OPTT=OPTT+1)) ;;
		d) set_input_archive "$(readlink -f "${OPTARG}")" && ((OPTT=OPTT+2)) ;;
	esac
done

# Check $OPTT for what the user wants to do
if [[ $OPTT -eq 0 ]]
then
	set_output_directory "$OUTDIR"  # the output directory flag is optional
	compress "$INDIR" "$OUTDIR"
elif [[ $OPTT -eq 1 ]]
then
	set_output_directory "$OUTDIR"  # the output directory flag is optional
	decompress "$INARC" "$OUTDIR"
else
	print_help
fi
